#ifndef SCRIPTS_H
#define SCRIPTS_H

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstdint>
#include <unordered_map>

#include <fmt/core.h>
#include <sw/redis++/redis++.h>
#include "attrs.h"

using namespace std;
using fmt::format;


/*
 * slot    : 当前时隙
 * nodes_id: 拓扑中所有节点的ID {1101,1102,...}
 * redis_cli  : Redis的客户端
 * link_delay : 链路延迟 "1101-1102":13450, 0表示该链路断开
 * scripts    : 更新链路及时延的脚本 "1101":"ovs-vsctl ....;tc qdisc ...;"
 * nodes_adjs : 节点的邻接点
 */
class TopoScript {
private:
    sw::redis::Redis redis_cli;
    void read_topo(void);               // 读取拓扑信息

public:
    uint32_t slot;
    map<string, vector<string>> nodes;  // <卫星ID，邻接卫星ID>
    map<string, int> link_delay;        // <链路ID，链路时延>
    map<string, string> scripts;        // <卫星ID，卫星脚本>

    TopoScript(): redis_cli(REDIS_TOPO){
        slot = 0;
        redis_cli.auth(REDIS_PASSWD);
        read_topo();
    }

    void update_link(void);             // 更新链路时延、通断情况
    void veth_pair_script(void);        // 生成创建 veth_pair 的脚本
    void ovs_init_script(void);         // 初始化 OVS 的脚本
};

void TopoScript::read_topo(void)
{
    string link_id;         // 链路ID，两节点ID组合 1101-1102
    vector<string> adjs;    // 邻接节点ID
    unordered_map<string, string> ump;

    redis_cli.hgetall(TOPOLOGY_KEY, std::inserter(ump, ump.begin()));
    for (auto& p : ump) {
        adjs = split(p.second, ',');
        nodes[p.first] = adjs;

        for (string &id : adjs) {
            link_id = p.first + "-" + id;
            if (p.first[0] == '2')
                link_delay[link_id] = 0;

            if (p.first < id)
                link_delay[link_id] = 0;
        }
    }

    fmt::print("拓扑信息>>>\n\t节点数：{}\n\t链路数：{}\n",
                nodes.size(), link_delay.size());
}


void TopoScript::update_link(void) {
    unordered_map<string, string> delay;
    string sw1, sw2, sw1_sw2, sw2_sw1;
    int d;

    redis_cli.hgetall(to_string(slot), std::inserter(delay, delay.begin()));
    for (auto &node : nodes)
        scripts[node.first] = "";

    // 遍历所有链路，更新链路延迟、通断情况
    for (auto &link : link_delay) {
        sw1 = link.first.substr(0, 4);
        sw2 = link.first.substr(5, 4);
        sw1_sw2 = sw1 + "-" + sw2;
        sw2_sw1 = sw2 + "-" + sw1;

        auto it = delay.find(link.first);
        if (it == delay.end() && link_delay[link.first] == 0) {
            // 当前时隙，链路断开，上个时隙链路已经断开，不需要更新

        } else if (it == delay.end() && link_delay[link.first] != 0) {
            // 当前时隙，链路断开，上个时隙链路还没断开，需要更新
            link_delay[link.first] = 0;
            // 给链路断开的两端发送消息，删除对应的端口
            scripts[sw1] += format(OVS_DEL_PORT, sw1_sw2);
            scripts[sw2] += format(OVS_DEL_PORT, sw2_sw1);

        } else if (it != delay.end() && link_delay[link.first] == 0) {
            // 当前时隙，链路恢复，上个时隙链路断开，需要更新
            d = int(stod(it->second) * 1000000);
            link_delay[link.first] = d;

            // 给链路恢复的两端发送消息
            scripts[sw1] += format(OVS_ADD_PORT, sw1, sw1_sw2, sw2);
            scripts[sw2] += format(OVS_ADD_PORT, sw2, sw2_sw1, sw1);
            scripts[sw1] += format(CHANGE_DELAY, sw1_sw2, d);
            scripts[sw2] += format(CHANGE_DELAY, sw2_sw1, d);
        #ifdef QDISC
            scripts[sw1] += format(OVS_SET_QDISC, sw1_sw2, OVS_QDISC);
            scripts[sw2] += format(OVS_SET_QDISC, sw2_sw1, OVS_QDISC);
        #endif

        } else if (it != delay.end() && link_delay[link.first] != 0) {
            // 当前时隙，链路正常，且上个时隙链路未断开
            d = int(stod(it->second) * 1000000);

            if (d != link_delay[link.first]) {
                link_delay[link.first] = d;
                // 更新链路延迟
                scripts[sw1] += format(CHANGE_DELAY, sw1_sw2, d);
                scripts[sw2] += format(CHANGE_DELAY, sw2_sw1, d);
            }
        }
    }
}

void TopoScript::veth_pair_script(void)
{
    string cmd;
    ofstream ofs;

    ofs.open(VETH_PAIR_FILE, ios::out|ios::trunc);
    for (const auto &id1 : nodes) {
        for (const string &id2 : id1.second) {
            if (id1.first < id2)    // 1101-1102 与 1102-1101 不需要重复创建
                continue;

            cmd = format(CMD_CREATE_VETH_PAIR, id1.first, id2);
            ofs << cmd;
        }
    }
    ofs.close();
}

void TopoScript::ovs_init_script(void)
{
    vector<string> ids = {"0x1", "0x2", "0x4", "0x8"};
    string ip, mac, filename, core_id, id, zeros = "";
    ofstream ofs;
    int cnt = 1;

    for (auto &node : nodes) {
        id = node.first;
        mac = format(MAC_ADDR, id.substr(0,2), id.substr(2, 2));
        ip = format(IP_ADDR, id.substr(0, 2), stoi(id.substr(2,2)));
        core_id = ids[cnt%4] + zeros;
        filename = format(OVS_INIT_FILE, id);

        ofs.open(filename, ios::out|ios::trunc);
        ofs << format(CMD_START_OVS, core_id, id, mac, ip, CONTROLLER_IP);
        ofs.close();

        if (cnt++ % 4 == 0)
            zeros.push_back('0');
    }
}

#endif
